<?php
/**
 * MySQL连接管理
 *
 * class DBHelper: MySQL读写分离连接管理类
 * function compose_url: 根据URL的各个部分组合完整的URL
 *
 * @author Lance Li<lance@6.cn>
 * @copyright 6.cn
 * @version $Id$
 */

/**
 * 读库类型ID
 */
define("DBHELPER_DBR", 1);

/**
 * 写库类型ID
 */
define("DBHELPER_DBW", 2);

/**
 * 读写库类型ID
 */
define("DBHELPER_DBALL", DBHELPER_DBR | DBHELPER_DBW);

/**
 * WEB激活方式类型ID
 */
define("DBHELPER_SCM_WEB", 1);

/**
 * CLI激活方式类型ID
 */
define("DBHELPER_SCM_CLI", 2);

/**
 * 根据提供的URL各个部分返回完整的URL串
 *
 * @param $schema 协议名称，比如 mysql, http, https, ftp
 * @param $user 用户名
 * @param $pass 用户口令
 * @param $host 主机名称，比如 127.0.0.1或者www.myweb.com
 * @param $port 端口号
 * @param $path 访问资源
 * @param $query 查询串
 * @param $fragment 定位点
 * @return 拼写完整的URL
 */
function compose_url($schema = NULL, $user = NULL, $pass = NULL, $host = NULL,
                     $port = NULL, $path = NULL, $query = NULL, $fragment = NULL)
{
    $url = "";

    if ($path && $path{0} != '/')
    {
        $path = '/' . $path;
    }

    if ($schema) $url .= $schema . "://";
    if ($user) $url .= $user;
    if ($pass) $url .= ":" . $pass;
    if ($user) $url .= "@";
    if ($host) $url .= $host;
    if ($port) $url .= ":" . $port;
    if ($path) $url .= $path;
    if ($query) $url .= "?" . $query;
    if ($fragment) $url .= "#" . $fragment;

    return $url;
}

/**
 * DBHelper: MySQL连接管理类，同时连接 memcache
 *
 * 本类实现了读写分离的数据库连接管理，客户可以不用关心操作的是哪个连接或者哪个库。
 * 查询的结果返回给客户管理，这主要是为了提供更大的灵活性。
 *
 * @todo 根据激活方式(WEB或者CLI)自动选择配置信息
 *
 */
class DBHelper
{
    /**
     * 调试模式(boolean)：true 调试；false 不调试(默认)
     * @access private
     */
    private $_debug;

    /**
     * 执行模式(boolean)：true 执行SQL范围检查(默认) false 不执行
     * @access private
     */
    private $_strict;

    /**
     * MySQL 读库连接句柄(resource)：默认NULL
     * @access private
     */
    private $_dbr_link;

    /**
     * MySQL 写库连接句柄(resource)：默认NULL
     * @access private
     */
    private $_dbw_link;

    /**
     * 读库连接信息(string)：形如 mysql://username:password@host:port/database
     * @access private
     */
    private $_dbr_url;

    /**
     * 写库连接信息(string)：形如 mysql://username:password@host:port/database
     * @access private
     */
    private $_dbw_url;

    /**
     * 运行模式(integer)：WEB模式(DBHELPER_SCM_WEB) 命令行模式(DBHELPER_SCM_WEB)
     * @access private
     */
    private $_scm;

    /**
     * 错误编号(integer)：负值表示错误行号，正值表示MySQL错误号
     * @access private
     */
    private $_errno;

    /**
     * 错误描述(string)：MySQL错误描述或者类代码错误
     * @access private
     */
    private $_errstr;

    /**
     * DBHelper构造函数
     *
     * @access public
     * @param $debug 是否启动调试模式
     * @param $strict 是否启动强制SQL范围检查
     */
    function __construct()
    {
        $this->_debug = false;
        $this->_strict = true;

        $this->_dbr_link = NULL;
        $this->_dbw_link = NULL;
        $this->_dbr_url = NULL;
        $this->_dbw_url = NULL;

        $this->_scm = isset($_SERVER['DOCUMENT_ROOT']) ? DBHELPER_SCM_WEB : DBHELPER_SCM_CLI;
        $this->_errno = 0;
        $this->_errstr = NULL;
    }

    /**
     * 连接MySQL数据库
     * @access public
     * @param $type 要连接的数据库类别
     * @param $permanent 是否打开永久连接
     * @return 成功返回true,否则返回false
     */
    function connect($type = DBHELPER_DBALL, $permanent = false)
    {
        $dbr_link = NULL;
        $dbw_link = NULL;
        if (!@$this->_prepareConnection($type)) return false;
        if ($type & DBHELPER_DBR)
        {
            @$dbr_link = $this->_connect($permanent, $this->_dbr_url);
            if (!$dbr_link) return false;
        }
        if ($type & DBHELPER_DBW)
        {
            @$dbw_link = $this->_connect($permanent, $this->_dbw_url);
            if (!$dbw_link)
            {
                if ($dbr_link) @mysqli_close($dbr_link);
                return false;
            }
        }
        if ($dbr_link) $this->_dbr_link = $dbr_link;
        if ($dbw_link) $this->_dbw_link = $dbw_link;
        return true;
    }

    /**
     * 断开数据库连接
     *
     * @access public
     * @param $type 断开的数据库连接类型
     *
     * @return 无返回值
     */
    function disconnect($type = DBHELPER_DBALL)
    {
        if ($type & DBHELPER_DBR)
        {
            if ($this->_dbr_link) @mysqli_close($this->_dbr_link);
            $this->_dbr_link = NULL;
        }

        if ($type & DBHELPER_DBW)
        {
            if ($this->_dbw_link) @mysqli_close($this->_dbw_link);
            $this->_dbw_link = NULL;
        }
    }

    /**
     * 选择数据库连接
     *
     * @access public
     * @param $type 选择数据库连接类型，默认选择所有连接类型
     * @param $dbname 数据库名
     *
     * @return 成功返回true,否则返回false
     */
    function selectDB($dbname, $type = DBHELPER_DBALL)
    {
        if (($type & DBHELPER_DBR) && !@mysqli_select_db($this->_dbr_link, $dbname))
        {
            //$this->_setError(mysqli_errno($this->_dbr_link), mysqli_error($this->_dbr_link));
            $this->_setError(mysqli_errno($this->_dbr_link), mysqli_error($this->_dbr_link));
            return false;
        }
        if (($type & DBHELPER_DBW) &&
            !@mysqli_select_db($this->_dbw_link, $dbname)
        )
        {
            $this->_setError(mysqli_errno($this->_dbw_link), mysqli_error($this->_dbw_link));
            return false;
        }

        return true;
    }

    /**
     * 获取MySQL连接句柄
     *
     * 警告：本函数破坏了类的封装性，尽量避免使用
     * @access public
     * @param $type 获取的数据库连接类型
     * @return 返回数据库连接句柄
     */
    function getDBLink($type)
    {
        if ($type == DBHELPER_DBR)
        {
            return $this->_dbr_link;
        }
        else if ($type == DBHELPER_DBW)
        {
            return $this->_dbw_link;
        }

        return NULL;
    }

    /**
     * 查询数据库
     *
     * @access public
     * @param $sql 待查询的SQL语句
     * @param $type 数据库连接类型，默认自动检测
     * @return 对于更新语句，返回true或者false，否则返回执行结果
     */
    function query($sql, $type = NULL)
    {
        $this->clear();
        if (!($check = $this->checkSQLType($sql)))
        {
            return false;
        }
        if (is_null($type)) $type = $check;
        if ($type == DBHELPER_DBR)
        {
            $dblink = $this->_dbr_link;
        }
        else if ($type == DBHELPER_DBW)
        {
            $dblink = $this->_dbw_link;
        }
        else
        {
            $this->_setError(-__LINE__, "DBLink type illegal: $type");
            return false;
        }
        mysqli_set_charset($dblink, 'utf8');
        $res = @mysqli_query($dblink, $sql);
        if (!$res)
        {
            $this->_setError(mysqli_errno($dblink), mysqli_error($dblink) . ":SQL:$sql");
        }
        else
        {
            if ($this->_debug)
            {
                $this->_debugMessage("QUERY:TYPE $type:SQL:$sql");
            }
        }
        return $res;
    }


    /*
        开启事物
    */
    function startTrans($type = NULL)
    {
        if (is_null($type)) $type = 2;
        if ($type == DBHELPER_DBR)
        {
            $dblink = $this->_dbr_link;
        }
        else if ($type == DBHELPER_DBW)
        {
            $dblink = $this->_dbw_link;
        }
        else
        {
            $this->_setError(-__LINE__, "DBLink type illegal: $type");
            return false;
        }
        mysqli_set_charset($dblink, 'utf8');
        mysqli_query($dblink, "SET AUTOCOMMIT=0"); // 设置为不自动提交，因为MYSQL默认立即执行
        mysqli_begin_transaction($dblink);            // 开始事务定义
        return true;
    }

    /*
        回滚事物
    */
    function rollback($type = NULL)
    {
        if (is_null($type)) $type = 2;
        if ($type == DBHELPER_DBR)
        {
            $dblink = $this->_dbr_link;
        }
        else if ($type == DBHELPER_DBW)
        {
            $dblink = $this->_dbw_link;
        }
        else
        {
            $this->_setError(-__LINE__, "DBLink type illegal: $type");
            return false;
        }
        mysqli_set_charset($dblink, 'utf8');
        mysqli_query($dblink, "ROLLBACK");           // 开始事物回滚
        return true;
    }

    /*
        提交事物
    */
    function commit($type = NULL)
    {
        if (is_null($type)) $type = 2;
        if ($type == DBHELPER_DBR)
        {
            $dblink = $this->_dbr_link;
        }
        else if ($type == DBHELPER_DBW)
        {
            $dblink = $this->_dbw_link;
        }
        else
        {
            $this->_setError(-__LINE__, "DBLink type illegal: $type");
            return false;
        }
        mysqli_set_charset($dblink, 'utf8');
        $res = mysqli_commit($dblink);
        return true;
    }

    /**
     * 将不需要即时返回结果的查询写log文件
     *
     * @access public
     * @param $sql 待查询的SQL语句
     * @return 是否记录成功
     */
    function query_to_file($sql, $tablename = '')
    {
        /*      if ( isset($_SERVER["SERVER_ADDR"]) )
                    $basedir = BGSQL_BASE_DIR.$_SERVER["SERVER_ADDR"];
                else
                    $basedir = BGSQL_BASE_DIR.'0.0.0.0';*/

        $basedir = BGSQL_BASE_DIR . mt_rand(0, 3);

        if ($tablename) $basedir .= '/' . $tablename;

        if (!is_dir($basedir)) `mkdir -p $basedir`;

        $fn = $basedir . '/' . uniqid('', true);

        if (!file_put_contents($fn, $sql))
            return false;
        else
            return true;
    }

    /**
     * 检查SQL语句的类型
     *
     * 本函数检查SQL语句的类型，如果是更新操作且strict模式打开，则做WHERE检查
     * 成功返回数据库连接类型，否则返回false
     *
     * @access public
     * @param $sql 待检查的SQL
     * @return 数据库连接类型, 或者false
     */
    function checkSQLType($sql)
    {
        $act_update = array("update", "delete", "replace", "insert",
            "create", "drop", "alter", "load", "flush", "kill", "lock",
            "grant", "revoke", "optimize");
        $act_check = array("update", "delete");

        $act = substr($sql, 0, 20);
        list($act) = explode(" ", trim($act));
        $act = strtolower($act);

        foreach ($act_update as $key)
        {
            if ($key == $act)
            {
                if ($this->_strict && (array_search($act, $act_check) !== false) &&
                    !stristr($sql, 'where')
                )
                {
                    $this->_setError(-__LINE__, "SQL checking failed:$act:$sql");
                    return false;
                }

                return DBHELPER_DBW;
            }
        }

        return DBHELPER_DBR;
    }

    /**
     * 改变调试模式
     *
     * @access public
     * @param $flag 将要改变的模式，默认仅仅查询模式状态
     * @return 调试模式
     */
    function debug($flag = NULL)
    {
        if (!is_null($flag)) $this->_debug = $flag;

        return $this->_debug;
    }

    /**
     * 改变执行模式
     *
     * @access public
     * @param $flag 将要改变的模式，默认仅仅查询模式状态
     * @return 执行模式
     */
    function strict($flag = NULL)
    {
        if (!is_null($flag)) $this->_strict = $flag;

        return $this->_strict;
    }

    /**
     * 是否发生错误
     *
     * @access public
     * @return 发生错误返回true，否则返回false
     */
    function error()
    {
        return ($this->_errno != 0);
    }

    /**
     * 取得错误代码
     *
     * @access public
     * @return 错误代码
     */
    function errno()
    {
        return $this->_errno;
    }

    /**
     * 取得错误描述
     *
     * @access public
     * @return 错误描述
     */
    function errstr()
    {
        return $this->_errstr;
    }

    /**
     * 清除发生的错误
     *
     * @access public
     */
    function clear()
    {
        $this->_errno = 0;
        $this->_errstr = NULL;
    }

    function _connect($permanent, $urlstring)
    {
        $url = parse_url($urlstring);

        //if (isset($url['port']))
        //{
        //    $url['host'] = $url['host'] . ':' . $url['port'];
        //}

        $port = isset($url['port']) ? $url['port'] : '3306';
        $dbname = substr($url['path'], 1);

        if ($permanent)
        {
            //$dbh = mysql_pconnect($url['host'], $url['user'], $url['pass'], true, MYSQL_CLIENT_COMPRESS);
            $dbh = mysqli_connect('p:' . $url['host'], $url['user'], $url['pass'], $dbname, $port);
        }
        else
        {
            //$dbh = mysql_connect($url['host'], $url['user'], $url['pass'], true, MYSQL_CLIENT_COMPRESS);
            $dbh = mysqli_connect($url['host'], $url['user'], $url['pass'], $dbname, $port);
        }

        if (!$dbh)
            if (!$dbh)
            {
                $this->_setError(mysqli_errno($dbh), mysqli_error($dbh) . " Failed connecting db: $urlstring");
//          $this->_setError(-__LINE__, "Failed connecting db: $urlstring");
                return false;
            }

        if ($dbname && !mysqli_select_db($dbh, $dbname))
        {
            $this->_setError(mysqli_errno($dbh), mysqli_error($dbh));
            mysqli_close($dbh);
            return false;
        }

        return $dbh;
    }

    function _setError($errno, $errstr)
    {
        $this->_errno = $errno;
        $this->_errstr = $errstr;

        if ($this->_debug)
        {
            $this->_debugMessage("ERROR:[$errno]$errstr");
        }
    }

    function _debugMessage($msg)
    {
        if ($this->_scm & DBHELPER_SCM_WEB)
        {
            error_log("DEBUG DB MESSAGE:" . $msg . "\n", 3, "/www/htdocs/okwww/app/v2/logs/DB" . date("Ymd") . ".txt");
        }
        else
        {
            error_log("DEBUG DB MESSAGE:" . $msg . "\n", 3, "/www/htdocs/okwww/app/v2/logs/DB" . date("Ymd") . ".txt");
        }
    }

    function _prepareConnection($type = DBHELPER_DBALL)
    {
        if ($type & DBHELPER_DBR) $this->_dbr_url = DBR_URL;
        if ($type & DBHELPER_DBW) $this->_dbw_url = DBW_URL;

        if ($this->_debug)
        {
            if ($type & DBHELPER_DBR)
            {
                $this->_debugMessage("DBR URL:" . $this->_dbr_url);
            }

            if ($type & DBHELPER_DBW)
            {
                $this->_debugMessage("DBW URL:" . $this->_dbw_url);
            }
        }
        return true;
    }

    function get_insert_id()
    {
        return mysqli_insert_id($this->_dbw_link);
    }

    function get_affected_rows()
    {
        return mysqli_affected_rows($this->_dbw_link);
    }

    function get_db_link($type = DBHELPER_DBW)
    {
        if ($type == DBHELPER_DBR)
        {
            return $this->_dbr_link;
        }
        else
        {
            return $this->_dbw_link;
        }
    }
}